﻿module RobertPiBook

open System.Collections.Generic
open Xunit
open FsUnit.Xunit


[<Fact>]
let ``Test throw not work``() = (fun () -> failwith "BOOM!" |> ignore) |> should throw typeof<System.Exception>


//114
[<Fact>]
let ``Should find person``()=
    
    let persons = ["pierre";"paul";"jack"]
    let rec findPerson person persons = 
        match persons with
        | h::t when h = person -> true
        | _::t -> findPerson person t
        | []-> false

    persons |> findPerson "paul" |> should equal true
    persons |> findPerson "henri" |> should equal false
    

//
[<Fact>]
let ``find string on page 81``()=
    let anotherList = [ box "one"; box 2; box 3.0 ]
    
    let recognizeAndPrintType (item : obj) =
        match item with
        | :? System.Int32 as x -> printfn "An integer: %i" x
        | :? System.Double as x -> printfn "A double: %f" x
        | :? System.String as x -> printfn "A string: %s" x
        | x -> printfn "An object: %A" x

    List.iter recognizeAndPrintType anotherList

[<Fact>]
let ``wrap bcl``()=
    
    let stringList = 
        let temp = new ResizeArray<string>() in
        temp.AddRange(["one";"two";"three"]);
        temp

    stringList.[0] |> should equal "one"

[<Fact>]
let ``map c# method to f#``()=

    let findIndex f arr = Array.findIndex f arr
    // define an array literal

    let rhyme = [| "The"; "cat"; "sat"; "on"; "the"; "mat" |]

    rhyme |> findIndex (fun s -> s.EndsWith("at")) |> should equal 1

[<Fact>]
let ``try array``()=
    let personArray = [|"pierre";"paul";"jack";"antoine"|]

    personArray.[0] |> should equal "pierre"
    personArray.[1] |> should equal "paul"

    personArray.[0] <- "hello"
    personArray.[1] <- "world"

    personArray.[0] |> should equal "hello"
    personArray.[1] |> should equal "world"

[<Fact>]
let ``Don't forget to patch FsUnit``()=
    let aFunction() = ()

    Assert.Equal((), ())

//    () |> should equal ()

[<Fact>]
let ``pattern matching test``()=
    let persons = ["pierre";"paul";"jack"]
    let rec findPerson p personList = 
        match personList with
        | h::t when h = p -> 1 + findPerson p t
        | _::t -> findPerson p t
        | []-> 0

    findPerson "paul" persons |> should equal 1

        

[<Fact>]
let ``share ref beetween func``()=
    let inc, dec, show =
        // define the shared state
        let n = ref 0
        // a function to increment
        let inc () =
            n := !n + 1
        // a function to decrement
        let dec () =
            n := !n - 1
        // a function to show the current state
        let show () =
            printfn "%i" !n
        // return the functions to the top level
        inc, dec, show
        // test the functions
    inc()
    inc()
    dec()
    show()

[<Fact>]
let ``test ref to mute``()=
    let totalArray () =
        // define an array literal
        let array = [| 1; 2; 3 |]
        // define a counter
        let total = ref 0
        // loop over the array
        for x in array do
        // kep a running total
        total := !total + x
        // print the total
        printfn "total: %i" !total
    totalArray()

[<Fact>]
let ``create lazy collection``()=
    let lazyList = Seq.unfold(fun x -> 
                        if x < 13 then Some(x, x + 1)
                        else None) 10
    
    printfn "%A" lazyList

[<Fact>]
let ``concat list``()=
    let rec concat l = 
        match l with
        | h::t  -> h @ (concat t)
        | []    -> []

    concat [["1";"2"];["3";"4"]]  |> should equal ["1";"2";"3";"4"]

[<Fact>]
let ``we can redefine type of identifier``()=
    let a = "toto"

    printfn "%s" a

    let a = 1

    printfn "%i" a

type 'a BinaryTree = 
    | BinaryNode of 'a BinaryTree * 'a BinaryTree
    | BinaryValue of 'a

    // represents an XML attribute
type XmlAttribute =
    { AttribName: string;
      AttribValue: string; }
// represents an XML element
type XmlElement =
    { ElementName: string;
      Attributes: list<XmlAttribute>;
      InnerXml: XmlTree }

and XmlTree =
    | Element of XmlElement
    | ElementList of list<XmlTree>
    | Text of string
    | Comment of string
    | Empty

[<Measure>]type liter
[<Measure>]type pint

//[<Fact>]
//let ``use memorystream and using like of c#``()=
//    use stream = new System.IO.MemoryStream
//    use writer = new System.IO.StreamWriter(stream)
//
//    writer.WriteLine("hello")
//    


[<Fact>]
let ``test unit of measure``()=

    let ratio = 1.0<liter> / 1.76056338<pint>

    let convertPintToLiter pints = pints * ratio

    convertPintToLiter 1.76056338<pint> |> should equal 1.0<liter>

[<Fact>]
let ``test try parse``()=
    let success, res = System.Int32.TryParse "1"

    success |> should equal true
    res |> should equal 1

    let success, res = System.Int32.TryParse "toto"

    success |> should equal false
    res |> should equal 0

[<Fact>]
let ``test binary tree``()=
    let tree1 = 
        BinaryNode(
            BinaryNode(BinaryValue 1, BinaryValue 2),
            BinaryNode(BinaryValue 3, BinaryValue 4))

    Assert.NotNull tree1


type Coin = 
    | TenCents
    | TweentyCents
    | FiftyCents
    | One
    | Two

let coinConverter m = 
    match m with
    | TenCents -> 0.1m
    | TweentyCents -> 0.2m
    | FiftyCents -> 0.5m
    | One -> 1.0m
    | Two -> 2.0m

let rec sumCoins l = 
    match l with
    | head::tail-> coinConverter head + sumCoins tail
    | [] -> 0.m

[<Fact>]
let ``insert 2 x TenCents is 0,20 credit``()=
    sumCoins [TenCents;TenCents] |> should equal 0.2m

type expectedValues = {money:Coin list;value:decimal}
[<Fact>]
let ``insert money and assert result``()=
    let moneysResult = 
        [{money=[TenCents;TenCents]; value=0.2m};
         {money=[TenCents;TweentyCents]; value=0.3m};
         {money=[TenCents;TweentyCents;FiftyCents]; value=0.8m};
         {money=[TenCents;TweentyCents;FiftyCents;One]; value=1.8m};
         {money=[TenCents;TweentyCents;FiftyCents;One;Two]; value=3.8m};
         {money=[]; value=0.m}]

    for mr in moneysResult do
        let actual=sumCoins mr.money
        actual |> should equal mr.value

type Couple = {him:string;her:string}
exception PartnerNotFoundException of string
[<Fact>]
let ``find partner``()=
    let couples = 
        [{him="Brad"; her="Angelina"};
         {him="Becks"; her="Posh"};
         {him="Chris"; her="Gwyneth"};
         {him="Michael";her="Catherine"}]

    let rec findPartner soughHer l =
        match l with
        | {him=x; her=her}::tail when her = soughHer -> x
        | _::tail -> findPartner soughHer tail
        | [] -> raise(PartnerNotFoundException("partner not found"))

    findPartner "Angelina" couples |> should equal "Brad"
    findPartner "Catherine" couples |> should equal "Michael"

    fun()->findPartner "toto" couples |> should throw typeof<PartnerNotFoundException>

exception PoshNotFoundException of string
[<Fact>]
let ``find couples``()=

    let couples = 
        [{him="Brad"; her="Angelina"};
         {him="Becks";her="Posh"};
         {him="Chris";her="Gwyneth"};
         {him="Michael";her="Catherine"}]

    let rec findPoshHusband l = 
        match l with
        | {him=x; her="Posh"}::tail->x
        | _::tail->findPoshHusband tail
        | [] -> raise(PoshNotFoundException("Posh husband not found"))

    findPoshHusband couples |> should equal "Becks"

[<Fact>]
let ``return pair``()=
    let evens n = 
        seq{for x in 1..n do 
            if x%2=0 then yield x}
    let actual = evens 10
    
    actual |> should equal actual

[<Fact>]
let ``create some list comprehension``()=
    let multipleOfThree = [0..3..30]
    multipleOfThree |> should equal [0; 3; 6; 9; 12; 15; 18; 21; 24; 27; 30]

[<Fact>]
let ``concat list to string with function``()=
    let rec concatFunction =
        function head::tail -> head + concatFunction tail
                | [] -> ""

    concatFunction ["toto,";"titi,";"tata"] |> should equal "toto,titi,tata"

[<Fact>]
let ``concat list to string``()=
    let rec concat l =
        match l with
        | head::tail -> head + concat tail
        | [] -> ""

    concat ["toto,";"titi,";"tata"] |> should equal "toto,titi,tata"

[<Fact>]
let ``Should make and operator``()=
    let And b = 
        match b with
        | true, true -> true
        | _ -> false
    
    And(true, true) |> should equal true
    And(true, false) |> should equal false
    And(false, false) |> should equal false
    And(false, true) |> should equal false

[<Fact>]
let ``Should maker or operator``()=
    let Or b = 
        match b with 
        | true, _ | _,true -> true
        | _ -> false

    Or(true,true) |> should equal true
    Or(true,false) |> should equal true
    Or(false,true) |> should equal true
    Or(false,false) |> should equal false

[<Fact>]
let ``Why FsUnit is awesome``()=
    let booleanToString b = match b with false -> "False" | _ -> "True"
    
    //Function assertion with FsUnit is awesome
    booleanToString false |> should equal "False"
    booleanToString true |> should equal "True"

    //Xunit only we have to supply information to compiler because he can
    //interpret string : IEnumerable<char> or string for type inference
    Assert.Equal<string>("False", booleanToString false)
    Assert.Equal<string>("True", booleanToString true)


exception BadStringException of string
[<Fact>]
let ``convert string to boolean``() = 
    let stringToBoolean s = 
        match s with 
        | "True" | "true"->true
        | "False" | "false"-> false
        | _ -> raise(BadStringException("not a boolean"))
    
    stringToBoolean "True" |> should equal true
    stringToBoolean "False" |> should equal false
    stringToBoolean "true" |> should equal true
    stringToBoolean "false" |> should equal false
    fun()->stringToBoolean "titi" |> should throw typeof<BadStringException>


[<Fact>]
let ``Should concat list``() =
    let listOfList = [[2;3;5]; [7;11;13]; [17;19;23;29]]

    let rec concatList l = 
        match l with
        | head :: tail -> head @ (concatList tail)
        | [] -> []

    
    let actual = concatList listOfList
    printfn "%A" actual

    actual.Length |> should equal 10

[<Fact>]
let ``Should find sequence in list``() = 
    let listWith123Begin = [1;2;3;4;5;6;7;8;9;3;2;1]
    let listWith123End = [0;2;3;4;5;6;7;8;9;1;2;3]
    let listWithout123 = [0;2;3;4;5;6;7;8;9;3;2;1]

    let rec findSequence l = 
        match l with
        | 1::2::3::_ -> true
        | head::tail -> findSequence tail
        | [] -> false

    findSequence listWith123Begin |> should equal true
    findSequence listWith123End |> should equal true
    findSequence listWithout123 |> should equal false